/*
 * Copyright 2024-2025 Embabel Software, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.embabel.agent.mcpserver.support

import com.embabel.common.core.types.Timestamped
import com.fasterxml.jackson.annotation.JsonPropertyDescription
import io.modelcontextprotocol.spec.McpSchema
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.*

// Test interfaces at top level
interface TestCustomInterface {
    val customField: String
}

class PromptUtilsTest {

    // Test data classes
    data class SimpleTestClass(
        val name: String,
        val age: Int
    )

    data class TestClassWithDescription(
        @JsonPropertyDescription("The user's full name")
        val name: String,
        @JsonPropertyDescription("The user's age in years")
        val age: Int,
        val email: String // No description
    )

    data class TestClassWithTimestamp(
        val name: String,
        val age: Int,
        override val timestamp: java.time.Instant
    ) : Timestamped

    data class TestClassWithSyntheticFields(
        val name: String,
        val age: Int
    ) {
        // Synthetic fields are typically generated by compiler
        // This simulates the scenario
    }

    class EmptyTestClass

    @Test
    fun `argumentsFromType should extract basic fields`() {
        val arguments = argumentsFromType(emptySet(), SimpleTestClass::class.java)

        assertEquals(2, arguments.size)

        val nameArg = arguments.find { it.name == "name" }
        assertNotNull(nameArg)
        assertEquals("name: String", nameArg!!.description)
        assertTrue(nameArg.required)

        val ageArg = arguments.find { it.name == "age" }
        assertNotNull(ageArg)
        assertEquals("age: Int", ageArg!!.description)
        assertTrue(ageArg.required)
    }

    @Test
    fun `argumentsFromType should use JsonPropertyDescription when available`() {
        val arguments = argumentsFromType(emptySet(), TestClassWithDescription::class.java)

        assertEquals(3, arguments.size)

        val nameArg = arguments.find { it.name == "name" }
        assertNotNull(nameArg)
        assertEquals("The user's full name: String", nameArg!!.description)

        val ageArg = arguments.find { it.name == "age" }
        assertNotNull(ageArg)
        assertEquals("The user's age in years: Int", ageArg!!.description)

        val emailArg = arguments.find { it.name == "email" }
        assertNotNull(emailArg)
        assertEquals("email: String", emailArg!!.description)
    }

    @Test
    fun `argumentsFromType should exclude fields from excluded interfaces`() {
        val excludedInterfaces = setOf(Timestamped::class.java)
        val arguments = argumentsFromType(excludedInterfaces, TestClassWithTimestamp::class.java)

        assertEquals(2, arguments.size)
        assertTrue(arguments.none { it.name == "timestamp" })
        assertTrue(arguments.any { it.name == "name" })
        assertTrue(arguments.any { it.name == "age" })
    }

    @Test
    fun `argumentsFromType should handle empty class`() {
        val arguments = argumentsFromType(emptySet(), EmptyTestClass::class.java)

        assertEquals(0, arguments.size)
    }

    @Test
    fun `argumentsFromType should handle multiple excluded interfaces`() {

        data class TestWithMultipleInterfaces(
            val name: String,
            override val timestamp: java.time.Instant,
            override val customField: String
        ) : Timestamped, TestCustomInterface

        val excludedInterfaces = setOf(Timestamped::class.java, TestCustomInterface::class.java)
        val arguments = argumentsFromType(excludedInterfaces, TestWithMultipleInterfaces::class.java)

        assertEquals(1, arguments.size)
        assertEquals("name", arguments[0].name)
    }

    @Test
    fun `argumentsFromType should set all fields as required`() {
        val arguments = argumentsFromType(emptySet(), SimpleTestClass::class.java)

        assertTrue(arguments.all { it.required })
    }

    @Test
    fun `argumentsFromType should handle various primitive types`() {
        data class PrimitiveTestClass(
            val stringField: String,
            val intField: Int,
            val longField: Long,
            val doubleField: Double,
            val booleanField: Boolean,
            val floatField: Float
        )

        val arguments = argumentsFromType(emptySet(), PrimitiveTestClass::class.java)

        assertEquals(6, arguments.size)

        val expectedTypes = mapOf(
            "stringField" to "String",
            "intField" to "Int",
            "longField" to "Long",
            "doubleField" to "Double",
            "booleanField" to "Boolean",
            "floatField" to "Float"
        )

        arguments.forEach { arg ->
            val expectedType = expectedTypes[arg.name]
            assertNotNull(expectedType, "Unexpected field: ${arg.name}")
            assertTrue(
                arg.description.endsWith(": $expectedType"),
                "Expected description to end with ': $expectedType' but was: ${arg.description}"
            )
        }
    }

    @Test
    fun `argumentsFromType should handle complex types`() {
        data class ComplexTestClass(
            val listField: List<String>,
            val mapField: Map<String, Int>,
            val optionalField: String?
        )

        val arguments = argumentsFromType(emptySet(), ComplexTestClass::class.java)

        assertEquals(3, arguments.size)
        assertTrue(arguments.any { it.name == "listField" })
        assertTrue(arguments.any { it.name == "mapField" })
        assertTrue(arguments.any { it.name == "optionalField" })
    }

    @Test
    fun `argumentsFromType should create valid McpSchema PromptArgument objects`() {
        val arguments = argumentsFromType(emptySet(), SimpleTestClass::class.java)

        arguments.forEach { arg ->
            // Verify it's a valid McpSchema.PromptArgument
            assertNotNull(arg.name)
            assertFalse(arg.name.isBlank())
            assertNotNull(arg.description)
            assertFalse(arg.description.isBlank())
            // All arguments should be required in this implementation
            assertTrue(arg.required)
        }
    }
}
